package spindles.api.db;

import static spindles.api.db.DB.runCmd;
import static spindles.api.db.DB.runTxn;

import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;

import spindles.api.util.ApplicationException;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.Transaction;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.PrimaryIndex;
import com.sleepycat.persist.SecondaryIndex;

public class DAO<E> {
	
	protected PrimaryIndex<Long, E> primaryIndex;
	protected SecondaryIndex<Long, Long, E> longSecondaryIndex;
	
	protected SecondaryIndex<String, Long, E> stringSecondaryIndex;
	
	private Transaction transaction;	

	public DAO(Class<E> entity) {		
		initPrim(entity);
	}
	
	public DAO(Class<E> entity, String keyName){		
		initPrim(entity);
		initSec(entity, Long.class, keyName);
	}
	
	public DAO (Class<E> entity, Class keyType, String keyName){		
		initPrim(entity);
		initSec(entity, keyType, keyName);	
	}
	
	
	public void setTransaction(Transaction txn){
		transaction = txn;
	}
	
	public Transaction getTransaction(){
		return transaction;
	}
	
	public void commit(){
		DB.commit(transaction);
	}
	
	public SortedSet<E> findAllSorted(){
		final SortedSet<E> result = new TreeSet<E>();
		loadAll(result);
		return result;
	}
	
	public SortedSet<E> findAllSorted(Comparator<E> c){
		final SortedSet<E> result = new TreeSet<E>(c);
		loadAll(result);
		return result;
	}
	
	public Set<E> findAll(){
		final Set<E> result = new HashSet<E>();		
		loadAll(result);		
		return result;
	}
	
	public Set<E> findAll(long parentID){
		final Set<E> result = new HashSet<E>();		
		loadAll(result, parentID);		
		return result;
	}
	
	public SortedSet<E> findAllSorted(long parentID){
		final SortedSet<E> result = new TreeSet<E>();
		loadAll(result, parentID);
		return result;
	}
	
	public SortedSet<E> findAllSorted(String key){
		final SortedSet<E> result = new TreeSet<E>();
		loadAll(result, key);
		return result;
	}
	
	public void deleteAll() {	
		runTxn(new Closeable<E>(primaryIndex){					
			public void execute(EntityCursor<E> cursor) throws DatabaseException {						
				while(cursor.next() != null) {
					cursor.delete();
				}
			}					
		});	
			
	}	
	
	private void loadAll(final Collection<E> c, final long parentID){		

		runCmd(new DBCommand() {
			public void execute() throws DatabaseException {
				final EntityCursor<E> entities = 
					longSecondaryIndex.subIndex(parentID).entities(null, DB.getCursorConfig());
				runCmd(new Closeable<E>(entities){					
					public void execute(EntityCursor<E> cursor) throws DatabaseException{						
						for(E e : cursor){
							c.add(e);
						}
					}					
				});
			}
		});		
	}
	
	private void loadAll(final Collection<E> c, final String key){		

		runCmd(new DBCommand() {
			public void execute() throws DatabaseException {
				final EntityCursor<E> entities = 
					stringSecondaryIndex.subIndex(key).entities(null, DB.getCursorConfig());
				runCmd(new Closeable<E>(entities){					
					public void execute(EntityCursor<E> cursor) throws DatabaseException{						
						for(E e : cursor){
							c.add(e);
						}
					}					
				});
			}
		});		
	}
	
	private void loadAll(final Collection<E> c){
		runCmd(new DBCommand(){
			public void execute() throws DatabaseException {
				final EntityCursor<E> entities = primaryIndex.entities(null, DB.getCursorConfig());
				runCmd(new Closeable<E>(entities){					
					public void execute(EntityCursor<E> cursor) throws DatabaseException{
						for(E e : cursor){
							c.add(e);
						}				
					}					
				});
			}
		});
	}
	
	private void initPrim(final Class<E> entity){		
		runCmd(new DBCommand(){
			public void execute() throws DatabaseException {				
				primaryIndex = 
					DB.getStore().getPrimaryIndex(Long.class, entity);			
			}
		});		
	}	
	
	private void initSec(final Class<E> entity, final Class keyType, final String keyName){		
		runCmd(new DBCommand(){
			public void execute() throws DatabaseException {
				if(keyType.equals(Long.class)){
					longSecondaryIndex = DB.getStore().
					getSecondaryIndex(primaryIndex, Long.class, keyName);
				} else if(keyType.equals(String.class)){
					stringSecondaryIndex = DB.getStore().
					getSecondaryIndex(primaryIndex, String.class, keyName);
				} else{
					throw new ApplicationException("Key Type not supported");
				}				
			}
		});		
	}

	public E put(final E entity){		
		return put(transaction, entity);
	}
	
	public E put(Transaction txn, final E entity){		
		try {
			return primaryIndex.put(txn, entity);
		} catch (DatabaseException e) {
			throw new ApplicationException(e);
		}		
	}
	
	public E get(Long pk){
		try {
			return primaryIndex.get(null, pk, LockMode.READ_UNCOMMITTED);
		} catch (DatabaseException e) {
			throw new ApplicationException(e);
		}
	}
	
	public SortedMap<Long, E> sortedMap(){
		return primaryIndex.sortedMap();
	}
	
}
